<template>
  <div
    class="editable-element-text"
    :class="{ lock: elementInfo.lock }"
    :style="{
      top: elementInfo.top + 'px',
      left: elementInfo.left + 'px',
      width: elementInfo.width + 'px',
      height: elementInfo.height + 'px',
    }"
  >
    <div
      class="rotate-wrapper"
      :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
    >
      <div
        class="element-content"
        ref="elementRef"
        :style="{
          width: elementInfo.vertical ? 'auto' : elementInfo.width + 'px',
          height: elementInfo.vertical ? elementInfo.height + 'px' : 'auto',
          backgroundColor: elementInfo.font.background,
          opacity: elementInfo.opacity,
          textShadow: shadowStyle,
          lineHeight: elementInfo.lineHeight,
          letterSpacing: (elementInfo.wordSpace || 0) + 'px',
          color: elementInfo.font.foreground,
          fontFamily: elementInfo.defaultFontName,
          writingMode: elementInfo.vertical ? 'vertical-rl' : 'horizontal-tb',
          fontSize:elementInfo.font.size
        }"
        v-contextmenu="contextmenus"
        @mousedown="($event) => handleSelectElement($event)"
        @touchstart="($event) => handleSelectElement($event)"
      >
        <ElementOutline
          :width="elementInfo.width"
          :height="elementInfo.height"
          :outline="elementInfo.outline"
        />
          <ProsemirrorEditor
            class="text"
            :elementId="elementInfo.id"
            :defaultColor="elementInfo.defaultColor"
            :defaultFontName="elementInfo.defaultFontName"
            :editable="!elementInfo.lock"
            :value="elementInfo.content"
            :style="{
              '--textIndent': `${elementInfo.textIndent || 0}px`,
              '--paragraphSpace': `${
                elementInfo.paragraphSpace === undefined
                  ? 5
                  : elementInfo.paragraphSpace
              }px`,
            }"
            @update="(value) => updateContent(value)"
            @mousedown="($event) => handleSelectElement($event, false)"
          />

        <!-- 当字号过大且行高较小时，会出现文字高度溢出的情况，导致拖拽区域无法被选中，因此添加了以下节点避免该情况 -->
        <div class="drag-handler top"></div>
        <div class="drag-handler bottom"></div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, onMounted, onUnmounted, PropType, ref, watch } from "vue";
import { storeToRefs } from "pinia";
import { debounce } from "lodash";
import { useMainStore, useSlidesStore } from "@/store";
import { PPTTextElement } from "@/types/slides";
import { ContextmenuItem } from "@/components/Contextmenu/types";
import useElementShadow from "@/views/components/element/hooks/useElementShadow";
import useHistorySnapshot from "@/hooks/useHistorySnapshot";

import ElementOutline from "@/views/components/element/ElementOutline.vue";
import ProsemirrorEditor from "@/views/components/element/ProsemirrorEditor.vue";
import { FontSize } from "@icon-park/vue-next";

const props = defineProps({
  elementInfo: {
    type: Object as PropType<PPTTextElement>,
    required: true,
  },
  selectElement: {
    type: Function as PropType<
      (
        e: MouseEvent | TouchEvent,
        element: PPTTextElement,
        canMove?: boolean
      ) => void
    >,
    required: true,
  },
  contextmenus: {
    type: Function as PropType<() => ContextmenuItem[] | null>,
  },
});

const mainStore = useMainStore();
const slidesStore = useSlidesStore();
const { handleElementId, isScaling } = storeToRefs(mainStore);

const { addHistorySnapshot } = useHistorySnapshot();

const elementRef = ref<HTMLElement>();

const shadow = computed(() => props.elementInfo.shadow);
const { shadowStyle } = useElementShadow(shadow);

const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
  if (props.elementInfo.lock) return;
  e.stopPropagation();

  props.selectElement(e, props.elementInfo, canMove);
};

// 监听文本元素的尺寸变化，当高度变化时，更新高度到vuex
// 如果高度变化时正处在缩放操作中，则等待缩放操作结束后再更新
const realHeightCache = ref(-1);
const realWidthCache = ref(-1);

watch(isScaling, () => {
  if (handleElementId.value !== props.elementInfo.id) return;

  if (!isScaling.value) {
    if (!props.elementInfo.vertical && realHeightCache.value !== -1) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { height: realHeightCache.value },
      });
      realHeightCache.value = -1;
    }
    if (props.elementInfo.vertical && realWidthCache.value !== -1) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { width: realWidthCache.value },
      });
      realWidthCache.value = -1;
    }
  }
});

const updateTextElementHeight = (entries: ResizeObserverEntry[]) => {
  const contentRect = entries[0].contentRect;
  if (!elementRef.value) return;

  const realHeight = contentRect.height + 20;
  const realWidth = contentRect.width + 20;

  if (!props.elementInfo.vertical && props.elementInfo.height !== realHeight) {
    if (!isScaling.value) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { height: realHeight },
      });
    } else realHeightCache.value = realHeight;
  }
  if (props.elementInfo.vertical && props.elementInfo.width !== realWidth) {
    if (!isScaling.value) {
      slidesStore.updateElement({
        id: props.elementInfo.id,
        props: { width: realWidth },
      });
    } else realWidthCache.value = realWidth;
  }
};
const resizeObserver = new ResizeObserver(updateTextElementHeight);

onMounted(() => {
  if (elementRef.value) resizeObserver.observe(elementRef.value);
});
onUnmounted(() => {
  if (elementRef.value) resizeObserver.unobserve(elementRef.value);
});

const updateContent = (content: string) => {
  slidesStore.updateElement({
    id: props.elementInfo.id,
    props: { content },
  });

  addHistorySnapshot();
};

const checkEmptyText = debounce(
  function () {
    const pureText = props.elementInfo.content.replaceAll(/<[^>]+>/g, "");
    if (!pureText) slidesStore.deleteElement(props.elementInfo.id);
  },
  300,
  { trailing: true }
);

const isHandleElement = computed(
  () => handleElementId.value === props.elementInfo.id
);
watch(isHandleElement, () => {
  if (!isHandleElement.value) checkEmptyText();
});
</script>

<style lang="scss" scoped>
.v-s-s {
  height: 30px;
  width: 200px;
  margin: 100px auto;
  overflow: hidden;
  font-size: 13px;

  .v-s-s_item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 0;
  }
}
.editable-element-text {
  position: absolute;

  &.lock .element-content {
    cursor: default;
  }
}
.rotate-wrapper {
  width: 100%;
  height: 100%;
}
.element-content {
  position: relative;
  padding: 10px;
  line-height: 1.5;
  word-break: break-word;
  cursor: move;

  .text {
    position: relative;
  }

  ::v-deep(a) {
    cursor: text;
  }
}
.drag-handler {
  height: 10px;
  position: absolute;
  left: 0;
  right: 0;

  &.top {
    top: 0;
  }
  &.bottom {
    bottom: 0;
  }
}
</style>
